#!/usr/bin/env python

# See the README for details on how this module works (if it's not obvious).
from util import attr

class ConformanceLevels(object):
    Basic = 0
    Optional = 1
    Intermediate = 2
    Advanced = 3
cl = ConformanceLevels()
    
class TypeMap(object):
    "Map types to those needed by the database."
    char = 'char'
    string = 'varchar'
    integer = 'integer'
    clob = 'clob'
    blob = 'blob'
    date = 'date'
    time = 'time'
    timestamp = 'timestamp'
    serial = 'serial'

    def __init__(self, **kw):
        """ Assign attributes based off parms."""
        for arg, value in kw.items():
            setattr(self, arg, value)
 
    
class DatabaseBase(object):
    """Describes the features that are known to vary between databases.
    This is a baseclass; subclass & set attributes as appropriate for
    your database.
    """

    def get_create_db_cmd(self, db_name):
        """ The command used to create a database. Return None when dbs are
        created implicitly.
        """
        raise NotImplementedError

    transaction_isolation = attr(True, 
        doc = "The dbms isolates uncommited transactions from read operations")

    transactional_ddl = attr(True, 
        doc = "DDL statements are transactional (need commit)")

    explicit_db_create = attr(True, 
        doc = "Databases are created explicitly (here for SQLite)")

    authentication = attr(True, 
        doc = "Database requires authentication (for SQLite)")
    
    time_datatype = attr(True,
        doc = "Database supports the time datatype (optional)",
        conformance_level = cl.Optional)

    time_datatype_subsecond = attr(True,
        doc = "The time datatype supports subsecond times")

    timestamp_datatype_subsecond = attr(True,
        doc = "The timestamp datatype supports subsecond times")

    stored_procedure_language = attr("SQL:2003",
        doc = "Stored procedure language can be one of: " 
              "Transact-SQL (Microsoft SQL Server), "
              "PL/SQL (Oracle), "
              "SQL/PL (DB2), "
              "PL/pgSQL (PostgreSQL), "
              "SQL:2003 (Anything standards compliant (MySQL))")

    lower_func = attr('lower',
        doc = "The name of the function used to convert a string to lowercase")

    scrollable_cursors = attr(False,
        doc = "Database supports scrollable cursors",
        conformance_level = cl.Advanced)

    auto_serial = attr(True, 
        doc = "Serial values are generated automatically when key" \
              " value is None",
        conformance_level = cl.Advanced)

    procedures = attr(True, 
        doc = "Database supports stored procedures",
        conformance_level = cl.Intermediate)

    procedures.return_results = attr(True, 
        doc = "Stored procedures can return results",
        conformance_level = cl.Intermediate)
 
    functions = attr(True, 
        doc = "Database supports user defined functions", 
        conformance_level = cl.Intermediate)


class sqlite(DatabaseBase):

    def get_create_db_cmd(self, db_name):
        return None

    explicit_db_create = False
    authentication = False
    time_datatype = True
    transactional_ddl = False
    procedures = False
    functions = False

    typemap = TypeMap()

class postgres(DatabaseBase):

    def get_create_db_cmd(self, db_name):
        return "psql -c 'create database %s'" % db_name

    stored_procedure_language = "PL/pgSQL"
    auto_serial = False
    procedures = False

    typemap = TypeMap(clob='text', blob='bytea')


class mysql(DatabaseBase):

    def get_create_db_cmd(self, db_name):
        raise NotImplementedError

    def __init__(self):
        self.procedures.return_results = False

    time_datatype_subsecond = True
    timestamp_datatype_subsecond = False
    transaction_isolation = False
    lower_func = False
  
    typemap = TypeMap(clob = 'text')


class IBMBase(DatabaseBase):
    def convert_connect_args(self, ci):
        if ci.port:
            args =[
               "DATABASE=%s;HOSTNAME=%s;PORT=%s;PROTOCOL=TCPIP;UID=%s;PWD=%s;"
               % (ci.database, ci.hostname, ci.port, ci.username, ci.password)
            ]
        else:
            args = ["DATABASE=%s;UID=%s;PWD=%s;"
                    % (ci.database, ci.name, ci.password)]


class db2(IBMBase):
    def get_create_db_cmd(self, db_name):
        return "db2 create database %s" % db_name

    typemap = TypeMap(serial = "int generated by default as identity")


class informix(IBMBase):
    """This is just a stub of what Informix support might look like.
    I don't have an informix DB to test with.
    """

    def get_create_db_cmd(self, db_name):
        return "create database %s" % db_name

    typemap = TypeMap(serial = "int", 
        timestamp = "datetime YEAR to FRACTION(5)", 
        time = "datetime HOUR to SECOND")
    #TODO: Insert 0 value to tell database to use next sequence number
    #typemap.serial = "serial"
       

#class ceODBC(DatabaseBase):
#    time_datatype = False
#    time_datatype_subsecond = False
    
#    typemap = TypeMap()
#    typemap.date = "datetime"
#    typemap.time = "integer"
#    typemap.serial = "integer"
#    typemap.clob = "text"
#    typemap.blob = "image"


class oracle(DatabaseBase):
    time_datatype = False
    time_datatype_time = False
    time_datatype_subsecond = False
   
    #TODO: To use a true serial, use sequence (seqname.nextval on inserts) 
    typemap = TypeMap(string = "varchar2", time = "number", serial = "number")

